/**
    ATClab - scriptable Air Traffic Control simulations.
    Copyright (C) 2010 openatclab@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#include "uicomponentbuilder.h"

#include "document.h"
#include "presenter.h"
#include "task.h"
#include "uicomponent.h"

#include <q3frame.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qpushbutton.h>
#include <qlineedit.h>
#include <qvalidator.h>
#include <qwidget.h>
//Added by qt3to4:
#include <Q3HBoxLayout>
#include <Q3BoxLayout>
#include <Q3VBoxLayout>

using namespace pact;

//------------------------------------------------------------------------------
// [public] class methods
//

/**
 *
 */
QWidget* UIComponentBuilder::build( 
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		QWidget *parent 
) {

	std::string name = doc->getAttributeValue( node, "idx" );

	//
	// built-in question types
	//

	if ( node->type.rfind( "_question" ) != std::string::npos ) 
			return UIComponentBuilder::build_question( doc, node, parent, name );


	//
	// layout components
	//
	// @todo: remove duplication (vbox/hbox)

	else if ( "hbox" == node->type ) {
		QWidget *hb = new Q3Frame( parent, name.c_str() );
		Q3BoxLayout *mgr = new Q3HBoxLayout( hb );

		for(
			xml::DocNodes::const_iterator child = node->children.begin();
			child != node->children.end();
			++child
		) {
			if ( QWidget *w = build( doc, *child, hb ) )
					mgr->addWidget( w );
		}

		return hb;
	}

	else if ( "vbox" == node->type ) {
		QWidget *box = new Q3Frame( parent, name.c_str() );
		Q3BoxLayout *mgr = new Q3VBoxLayout( box );

		for(
			xml::DocNodes::const_iterator child = node->children.begin();
			child != node->children.end();
			++child
		) {
			if ( QWidget *w = build( doc, *child, box ) )
					mgr->addWidget( w );
		}

		return box;
	}

	//
	// layout formatting
	//

	else if ( "space" == node->type ) {
		Q3BoxLayout *bl = dynamic_cast< Q3BoxLayout * >( parent->layout() );
		if ( bl ) 
				bl->addSpacing( doc->getIntegerAttribute( node, "size" ) );
		return NULL;
	}
	
	else if ( "stretch" == node->type ) {
		Q3BoxLayout *bl = dynamic_cast< Q3BoxLayout * >( parent->layout() );
		if ( bl ) bl->addStretch();
		return NULL;
	}

	//
	// widget components
	//

	else if ( "label" == node->type ) {
		QLabel *l = new QLabel( parent, name.c_str() );
		l->setText( doc->getAttributeValue( node, "text" ).c_str() );
		l->setFrameStyle( Q3Frame::Panel|Q3Frame::Sunken );
		l->setAlignment( Qt::AlignCenter );
		
		return l;
	}
	
	else if ( "pushbutton" == node->type ) {
		QPushButton *pb = new QPushButton( 
				doc->getAttributeValue( node, "idx" ).c_str(),
				parent,
				name.c_str()
			);
		pb->setText( doc->getAttributeValue( node, "text" ).c_str() );
		pb->setDefault( true );
		return pb;
	}

	else if ( "textbox" == node->type ) {
		QLineEdit *tb = new QLineEdit( parent, name.c_str() );
		process_properties( doc, node, tb );
		//tb->setText( "hello" );
		return tb;
	}
	
	//
	// reference?
	//

	else if ( "ui_component" == node->type ) {
		return UIComponentBuilder::build(
				doc,
				doc->getReferencedNode( doc->getAttributeValue( node, "ref" ) ),
				parent
			);
	}

	else {
		throw uicomponentbuilder_error( 
				"cannot build: illegal node <" + node->type + ">" 
			);
	}
}


//--------------------------------------------------------------------------------------------------
// [private]
//

/**
 * Construct Questions
 *
 * \todo refactor to remove dynamic_casts and repetition.
 */
QWidget* UIComponentBuilder::build_question( 
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		QWidget *parent,
		const std::string &name
) {
	Question *question = 0;

	if ( "choice_question" == node->type ) {
		question = new ChoiceQuestion( parent, name );
		for (
			xml::DocNodes::const_iterator child = node->children.begin();
			child != node->children.end();
			++child
		) {
			process_choice_question( doc, *child, dynamic_cast<ChoiceQuestion*>(question) );
		}
	}

	else if ( "multi_question" == node->type ) {
		question = new MultiQuestion( parent, name );
		for (
			xml::DocNodes::const_iterator child = node->children.begin();
			child != node->children.end();
			++child
		) {
			process_multi_question( doc, *child, dynamic_cast<MultiQuestion*>(question) );
		}
	}

	else if ( "scale_question" == node->type ) {
		question = new ScaleQuestion( parent, name );
		for (
			xml::DocNodes::const_iterator child = node->children.begin();
			child != node->children.end();
			++child
		) {
			process_scale_question( doc, *child, dynamic_cast<ScaleQuestion*>(question) );
		}
	}

	else if ( "validated_question" == node->type ) 
	{
		ValidationQuestionType vqType = VALIDATION_NORMAL;
		
		if (doc->attributeExists(node, "type"))
		{
			std::string type = doc->getAttributeValue(node, "type");

			if (type == "srGoal")
			{
				vqType = VALIDATION_SR_GOAL;
			}
			else if (type == "normGoal")
			{
				vqType = VALIDATION_NORM_GOAL;
			}
		}

		question = new ValidatedQuestion( parent, name, vqType);

		for (
			xml::DocNodes::const_iterator child = node->children.begin();
			child != node->children.end();
			++child
		) {
			process_validated_question( doc, *child, dynamic_cast<ValidatedQuestion*>(question) );
		}
	}
	
	question->construct();	// @todo: can we put off till later?

	//
	// search ancestory for Task
	//

	QObject* obj = question;
	while ( !obj->inherits("pact::Task") ) obj = obj->parent();
	Task* task = dynamic_cast<Task*>(obj);

	//
	// connect up logging
	//

	connect( 
			question, SIGNAL( sig_data(logging::KVPairs &) ), 
			task, SLOT( log_data(logging::KVPairs &) ) 
		);

	return question;

}




/**
 *
 */
void UIComponentBuilder::process_properties( 
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		QWidget *widget 
) {
	for(
		xml::DocNodes::const_iterator child = node->children.begin();
		child != node->children.end();
		++child
	) {
		if ( "int_validator" == (*child)->type ) {
			QLineEdit *le = dynamic_cast< QLineEdit * >( widget );
			if ( ! le )
					throw uicomponentbuilder_error( 
							"an integer validator only applies to a line edit!"
						);
			le->setValidator( new QIntValidator(
					doc->getIntegerAttribute( *child, "minimum" ),
					doc->getIntegerAttribute( *child, "maximum" ),
					widget
				) );
		}
	}
}


//------------------------------------------------------------------------------
// [private] build built-in quesiton
//

/**
 *
 */
void UIComponentBuilder::process_base_question( 
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		BaseQuestion *question 
) {
	if ( "text" == node->type ) {
		int x = -1;
		if (doc->attributeExists(node, "x"))
		{
			x = atoi(doc->getAttributeValue(node, "x").c_str());
		}

		int y = -1;
		if (doc->attributeExists(node, "y"))
		{
			y = atoi(doc->getAttributeValue(node, "y").c_str());
		}

		question->set_text( doc->getStringContent(node), x, y );
	}

	else if ( "label" == node->type ) {
		question->add_label( doc->getStringContent(node) );
	}
}

/**
 *
 */
void UIComponentBuilder::process_choice_question( 
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		ChoiceQuestion *question 
) {
	if ( "choice" == node->type ) 
	{
		int value = -1;

		if (doc->attributeExists(node, "value"))
		{
			value = atoi(doc->getAttributeValue(node, "value").c_str());
		}

		std::string idx;

		if (doc->attributeExists(node, "idxref"))
		{
			idx = doc->getAttributeValue(node, "idxref");
		}

		int x = -1;
		if (doc->attributeExists(node, "x"))
		{
			x = atoi(doc->getAttributeValue(node, "x").c_str());
		}

		int y = -1;
		if (doc->attributeExists(node, "y"))
		{
			y = atoi(doc->getAttributeValue(node, "y").c_str());
		}

		int w = -1;
		if (doc->attributeExists(node, "w"))
		{
			w = atoi(doc->getAttributeValue(node, "w").c_str());
		}

		int h = -1;
		if (doc->attributeExists(node, "h"))
		{
			h = atoi(doc->getAttributeValue(node, "h").c_str());
		}

		question->add_choice(doc->getStringContent(node), value, idx, x, y, w, h);
	}
	else 
	{
		process_base_question( doc, node, question );
	}
}

/**
 *
 */
void UIComponentBuilder::process_base_scale_question( 
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		BaseScaleQuestion *question 
) {
	if ( "scale" == node->type ) {
		question->set_scale(
				doc->getIntegerAttribute( node, "min" ),
				doc->getIntegerAttribute( node, "max" )
			);
	}

	else {
		process_base_question( doc, node, question );
	}
}

/**
 *
 */
void UIComponentBuilder::process_scale_question( 
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		ScaleQuestion *question 
) {
	process_base_scale_question( doc, node, question );
}

/**
 *
 */
void UIComponentBuilder::process_multi_question(
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		MultiQuestion *question
) {
	if ( "question" == node->type ) {
		question->add_question( doc->getStringContent(node) );
	}

	else if ( "number" == node->type ) {
		question->set_numbered();
	}

	else if ( "answer_all" == node->type ) {
		question->set_must_answer();
	}

	else {
		process_base_scale_question( doc, node, question );
	}
}

/**
 *
 */
void UIComponentBuilder::process_validated_question(
		const xml::Document *doc,
		const xml::DocumentNode *node, 
		ValidatedQuestion *question
) {
	if ( "text" == node->type ) {
		question->set_text( doc->getStringContent(node) );
	}

	else if ( "int_validator" == node->type ) {
		question->set_validator( new QIntValidator(
				doc->getIntegerAttribute( node, "min" ),
				doc->getIntegerAttribute( node, "max" ),
				question
			) );
		question->set_validator_message( 
				doc->getStringContent( node, "text" )
			);
	}

	else if ( "link" == node->type ) {
		std::string global = doc->getAttributeValue( node, "global" );
		Presenter *p = Presenter::instance();
		GlobalVariables vars = p->globals();
		Global *v = vars[global];
		QObject::connect( question, SIGNAL( sig_accepted(int) ), v, SLOT( set_value(int) ) );
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////
